﻿//******************************************************************************************************
//  FileBlockWriter.cs - Gbtc
//
//  Copyright © 2013, Grid Protection Alliance.  All Rights Reserved.
//
//  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
//  the NOTICE file distributed with this work for additional information regarding copyright ownership.
//  The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
//  not use this file except in compliance with the License. You may obtain a copy of the License at:
//
//      http://www.opensource.org/licenses/eclipse-1.0.php
//
//  Unless agreed to in writing, the subject software distributed under the License is distributed on an
//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
//  License for the specific language governing permissions and limitations.
//
//  Code Modification History:
//  ----------------------------------------------------------------------------------------------------
//  04/26/2013 - Stephen C. Wills
//       Generated original version of source code.
//
//******************************************************************************************************

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using GSF;
using GSF.IO;
using SIEGate.Core;
using SIEGate.Core.Adapters;
using SIEGate.Core.Configurator;
using SIEGate.Core.Measurements.Values;

namespace FileAdapters
{
    /// <summary>
    /// Output adapter that receives blocks of data as measurements and writes them to a file.
    /// </summary>
    [Description("FileBlockWriter: Receives buffer block measurements and writes them to files.")]
    public class FileBlockWriter : NetworkCommunicator
    {
        #region [ Members ]

        // Nested Types
        public class NetworkCommunicatorInfo : NetworkCommunicatorInfoBase
        {
            #region [ Members ]

            // Fields
            private string m_outputDirectory;

            #endregion

            #region [ Properties ]

            /// <summary>
            /// Gets or sets the directory to which files are written.
            /// </summary>
            [ConnectionStringParameter,
            Description("Defines the directory to which files are written.")]
            public string OutputDirectory
            {
                get
                {
                    return m_outputDirectory;
                }
                set
                {
                    m_outputDirectory = value;
                }
            }

            #endregion

            #region [ Methods ]

            public override void ExtractConfiguration(System.Data.DataSet configurationTables)
            {
                const string errorMessage = @"{0} is missing from Settings - Example: outputDirectory=C:\Files";
                Dictionary<string, string> settings;
                string setting;

                base.ExtractConfiguration(configurationTables);
                settings = Settings;

                // Required parameters

                if (!settings.TryGetValue("outputDirectory", out setting))
                    throw new ArgumentException(string.Format(errorMessage, "outputDirectory"));

                m_outputDirectory = FilePath.GetAbsolutePath(setting);

                if (!Directory.Exists(m_outputDirectory))
                    Directory.CreateDirectory(m_outputDirectory);
            }

            #endregion
        
        }

        // Fields
        private FileStream m_activeFileStream;
        private long m_activeFileSize;
        private long m_bytesWritten;

        private bool m_enabled;
        private bool m_disposed;
        public const string StrStatBytesWritten = "Bytes Written";

        #endregion

        #region [ Constructors ]

        public FileBlockWriter(IMessageBusProxy busProxy, NetworkCommunicatorInfo conf)
            : base(busProxy, conf)
        {
            m_statsCluster.RegisterCounter(conf.Name, StrStatBytesWritten);
        }

        /// <summary>
        /// Releases the unmanaged resources before the <see cref="FileBlockWriter"/> object is reclaimed by <see cref="GC"/>.
        /// </summary>
        ~FileBlockWriter()
        {
            Dispose(false);
        }

        #endregion

        #region [ Properties ]

        /// <summary>
        /// Gets or sets the adapter info for this adapter.
        /// </summary>
        public new NetworkCommunicatorInfo Info
        {
            get
            {
                return (NetworkCommunicatorInfo)base.Info;
            }
            set
            {
                base.Info = value;
            }
        }

        /// <summary>
        /// Gets or sets the directory to which files are written.
        /// </summary>
        [ConnectionStringParameter,
        Description("Defines the directory to which files are written.")]
        public string OutputDirectory
        {
            get
            {
                return Info.OutputDirectory;
            }
            set
            {
                Info.OutputDirectory = value;
            }
        }

        #endregion

        #region [ Methods ]

        public override void Start()
        {
            IMeasurement[] measurements;

            m_enabled = true;
            MessageBusProxy.Subscribe(Info.IncomingStreamIDs);

            while (m_enabled)
            {
                measurements = MessageBusProxy.Recv();

                foreach (IMeasurement measurement in measurements)
                    HandleMeasurement(measurement);
            }
        }

        public override void Stop()
        {
            m_enabled = false;
        }

        /// <summary>
        /// Gets a short one-line status of this <see cref="FileBlockWriter"/>.
        /// </summary>
        /// <param name="maxLength">Maximum number of available characters for display.</param>
        /// <returns>A short one-line summary of the current status of this <see cref="AdapterBase"/>.</returns>
        public string GetShortStatus(int maxLength)
        {
            if ((object)m_activeFileStream != null)
                return string.Format("Currently writing to file {0}", Path.GetFileName(m_activeFileStream.Name)).CenterText(maxLength);

            return string.Format("{0} files written by {1}", FilePath.GetFileList(Path.Combine(OutputDirectory, "*")).Length, Info.Name).CenterText(maxLength);
        }

        /// <summary>
        /// Writes buffer blocks out to files.
        /// </summary>
        private void HandleMeasurement(IMeasurement m)
        {
            //m_statsCluster.AddToCounter(StrStatProcessedMeasurements, 1);

            BufferBlockValue bufferBlockValue = m.Value as BufferBlockValue;

            // Only buffer block measurements can be processed
            if ((object)bufferBlockValue != null)
                ProcessBufferBlockMeasurement(bufferBlockValue);
        }

        private void ProcessBufferBlockMeasurement(BufferBlockValue value)
        {
            Tuple<byte[], int> bufferBlock = value.GetRaw();
            byte[] buffer = bufferBlock.Item1;
            int index = 1;

            if (buffer[0] != 0)
            {
                // Start of a new file - read file info
                int fileNameByteLength = EndianOrder.BigEndian.ToInt32(buffer, 1);
                string fileName = Encoding.Unicode.GetString(buffer, 5, fileNameByteLength);
                long fileSize = EndianOrder.BigEndian.ToInt64(buffer, 5 + fileNameByteLength);

                // Notify of new file creation
                LogDispatcher.I("FileBlockWriter", "", "Now writing to file {0}...", fileName);

                // Create new file
                using (FileStream activeFileStream = m_activeFileStream)
                    m_activeFileStream = File.Create(Path.Combine(OutputDirectory, fileName));

                m_activeFileStream.SetLength(fileSize);
                m_activeFileSize = fileSize;
                m_bytesWritten = 0L;

                // Advance buffer pointer to file data
                index = 1 + 4 + fileNameByteLength + 8;
            }

            if ((object)m_activeFileStream != null)
            {
                // Write data into the file
                int bytesOfData = bufferBlock.Item2 - index;
                m_activeFileStream.Write(buffer, index, bytesOfData);
                m_bytesWritten += bytesOfData;
                m_statsCluster.AddToCounter(StrStatBytesWritten, bytesOfData);

                // Close the file when we detect that
                // we've written all bytes to the file
                if (m_bytesWritten >= m_activeFileSize)
                {
                    LogDispatcher.I("FileBlockWriter", "", "Finished writing to file {0}.", Path.GetFileName(m_activeFileStream.Name));
                    m_activeFileStream.Dispose();
                    m_activeFileStream = null;
                    m_bytesWritten = 0L;
                }
            }
        }

        /// <summary>
        /// Releases all the resources used by the <see cref="FileBlockWriter"/> object.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases the unmanaged resources used by the <see cref="FileBlockWriter"/> object and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!m_disposed)
            {
                try
                {
                    if (disposing)
                    {
                        if ((object)m_activeFileStream != null)
                        {
                            m_activeFileStream.Dispose();
                            m_activeFileStream = null;
                        }
                    }
                }
                finally
                {
                    m_disposed = true;  // Prevent duplicate dispose.
                }
            }
        }

        #endregion
    }
}
